1. Import test data¶

In [ ]:
import pandas as pd
import pandas_ta as ta
from statsmodels.nonparametric.kernel_regression import KernelReg
import numpy as np
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from datetime import datetime
import matplotlib.pyplot as plt
from backtesting import Strategy
from backtesting import Backtest
import seaborn as sns

2. Load in the data¶

In [ ]:
df = pd.read_csv("EURUSD_Candlestick_15_M_BID_01.02.2023-17.02.2024.csv") # read in the data
df["Gmt time"]=df["Gmt time"].str.replace(".000","", regex=False) # remove the milliseconds; mention that we are not using regex but a simple string replace
df['Gmt time']=pd.to_datetime(df['Gmt time'],format='%d.%m.%Y %H:%M:%S') # convert the time string to datetime object
df=df[df.High!=df.Low] # remove rows where High=Low

3. Define the EMAs and ATR¶

In [ ]:
df["EMA_slow"]=ta.ema(df.Close, length=50)
df["EMA_fast"]=ta.ema(df.Close, length=40)
df['ATR']=ta.atr(df.High, df.Low, df.Close, length=7)
df
Out[ ]:
Gmt time Open High Low Close Volume EMA_slow EMA_fast ATR
0 2023-02-01 00:00:00 1.08605 1.08619 1.08583 1.08604 2184.41 NaN NaN NaN
1 2023-02-01 00:15:00 1.08604 1.08623 1.08583 1.08609 2373.76 NaN NaN NaN
2 2023-02-01 00:30:00 1.08608 1.08637 1.08604 1.08630 1649.21 NaN NaN NaN
3 2023-02-01 00:45:00 1.08629 1.08638 1.08606 1.08622 2071.47 NaN NaN NaN
4 2023-02-01 01:00:00 1.08622 1.08634 1.08594 1.08607 2021.66 NaN NaN NaN
... ... ... ... ... ... ... ... ... ...
18190 2024-02-16 20:45:00 1.07744 1.07770 1.07729 1.07764 3288.91 1.076952 1.077122 0.000443
18191 2024-02-16 21:00:00 1.07764 1.07771 1.07747 1.07763 1677.19 1.076979 1.077147 0.000414
18192 2024-02-16 21:15:00 1.07763 1.07778 1.07758 1.07776 964.40 1.077010 1.077176 0.000384
18193 2024-02-16 21:30:00 1.07777 1.07778 1.07761 1.07772 1957.77 1.077038 1.077203 0.000353
18194 2024-02-16 21:45:00 1.07773 1.07775 1.07747 1.07749 1693.33 1.077055 1.077217 0.000343

18176 rows × 9 columns

4. Define Nadaraya Watson Strategy¶

In [ ]:
dfsample = df[0:] # create a shadow coppy of the df. For simplicity, we can use part of the data.

X = dfsample.index # extract the index (time) of df and store it as variable X
# initialise the kernel regression model for nadaraya-watson kernel regression
# endog is the dependent variable, exog is the independent variable, var_type is the type of the variables, reg_type is the type of regression, bw is the bandwidth
# - Endogenous (dependent) variable: Close price
# - Exogenous (independent) variable: Time
# - Variable type: 'c' for continuous
# - Regression type: 'lc' for local constant another option is 'll' for local linear
# - Bandwidth: 3; the bandwidth is the window size of the kernel, a larger bandwidth will smooth the curve more a smaller bandwidth will make the curve more wiggly following the current data more closely
model = KernelReg(endog=dfsample['Close'], exog=dfsample.index, var_type='c', reg_type='lc', bw=[3])
# - fittd_values is the predicted values of the Close price
# - marginal_effects is the marginal effects in the Close price. I.e how a small change in the time will affect the Close price
fitted_values, marginal_effects = model.fit() # fit the model


# Add fitted values to DataFrame
dfsample['NW_Fitted'] = fitted_values

# Calculate residuals
# difference between the actual Close price and the predicted Close price; useful in understanding how well the model fits the data
residuals = dfsample['Close'] - fitted_values

# Calculate standard deviation of residuals
# standard deviation of the residuals is a measure of the spread of the residuals
# it is used to calculate the upper and lower envelopes
std_dev = 2.*np.std(residuals)
#std_dev = dfsample['Close'].rolling(window=30).std()

# Calculate upper and lower envelopes
dfsample['Upper_Envelope'] = dfsample['NW_Fitted'] + std_dev
dfsample['Lower_Envelope'] = dfsample['NW_Fitted'] - std_dev

dfsample[0:100]
/tmp/ipykernel_11488/2884411395.py:18: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  dfsample['NW_Fitted'] = fitted_values
/tmp/ipykernel_11488/2884411395.py:31: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  dfsample['Upper_Envelope'] = dfsample['NW_Fitted'] + std_dev
/tmp/ipykernel_11488/2884411395.py:32: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the caveats in the documentation: https://pandas.pydata.org/pandas-docs/stable/user_guide/indexing.html#returning-a-view-versus-a-copy
  dfsample['Lower_Envelope'] = dfsample['NW_Fitted'] - std_dev
Out[ ]:
Gmt time Open High Low Close Volume EMA_slow EMA_fast ATR NW_Fitted Upper_Envelope Lower_Envelope
0 2023-02-01 00:00:00 1.08605 1.08619 1.08583 1.08604 2184.41 NaN NaN NaN 1.086118 1.087100 1.085137
1 2023-02-01 00:15:00 1.08604 1.08623 1.08583 1.08609 2373.76 NaN NaN NaN 1.086109 1.087091 1.085127
2 2023-02-01 00:30:00 1.08608 1.08637 1.08604 1.08630 1649.21 NaN NaN NaN 1.086089 1.087071 1.085107
3 2023-02-01 00:45:00 1.08629 1.08638 1.08606 1.08622 2071.47 NaN NaN NaN 1.086055 1.087037 1.085073
4 2023-02-01 01:00:00 1.08622 1.08634 1.08594 1.08607 2021.66 NaN NaN NaN 1.086005 1.086987 1.085023
... ... ... ... ... ... ... ... ... ... ... ... ...
95 2023-02-01 23:45:00 1.10115 1.10129 1.10091 1.10124 1534.00 1.094777 1.095766 0.001213 1.101290 1.102272 1.100308
96 2023-02-02 00:00:00 1.10123 1.10182 1.10102 1.10151 4177.33 1.095041 1.096046 0.001154 1.101447 1.102428 1.100465
97 2023-02-02 00:15:00 1.10151 1.10218 1.10126 1.10192 2184.37 1.095311 1.096333 0.001120 1.101539 1.102521 1.100557
98 2023-02-02 00:30:00 1.10191 1.10237 1.10152 1.10220 4100.23 1.095581 1.096619 0.001082 1.101579 1.102561 1.100597
99 2023-02-02 00:45:00 1.10223 1.10266 1.10185 1.10185 4133.15 1.095827 1.096874 0.001043 1.101579 1.102561 1.100597

100 rows × 12 columns

5. Define the Bolinger Bands Calculation¶

In [ ]:
my_bbands = ta.bbands(dfsample.Close, length=30, std=2) # calculate the bollinger bands for the closing prices; lenght 30 means it looks at last 30 bars, std=2 means it uses 2 standard deviations
dfsample=dfsample.join(my_bbands)
#1.105424	1.097329
In [ ]:
dfsample.columns
Out[ ]:
Index(['Gmt time', 'Open', 'High', 'Low', 'Close', 'Volume', 'EMA_slow',
       'EMA_fast', 'ATR', 'NW_Fitted', 'Upper_Envelope', 'Lower_Envelope',
       'BBL_30_2.0', 'BBM_30_2.0', 'BBU_30_2.0', 'BBB_30_2.0', 'BBP_30_2.0'],
      dtype='object')

6. Nadaraya Watson Envelopes Visualization¶

In [ ]:
# Create a plot with 1 row
fig = make_subplots(rows=1, cols=1)

# Add candlestick plot with customized line colors
fig.add_trace(go.Candlestick(x=dfsample.index,
                             open=dfsample['Open'],
                             high=dfsample['High'],
                             low=dfsample['Low'],
                             close=dfsample['Close'],
                             increasing=dict(line=dict(color='rgba(0, 255, 0, 0.6)', width=0.1), # Red with transparency for increasing
                                             fillcolor='rgba(0, 255, 0, 0.6)'),  # Match fill color with line color
                             decreasing=dict(line=dict(color='rgba(255, 0, 0, 0.6)', width=0.1), # Green with transparency for decreasing
                                             fillcolor='rgba(255, 0, 0, 0.6)')), # Match fill color with line color
              row=1, col=1)

# Add Nadaraya-Watson fitted line
fig.add_trace(go.Scatter(x=dfsample.index, y=dfsample['NW_Fitted'],
                         line=dict(color='green', width=2),
                         name="Nadaraya-Watson Fit"),
              row=1, col=1)

# Add upper standard deviation envelope
fig.add_trace(go.Scatter(x=dfsample.index, y=dfsample['Upper_Envelope'],
                         line=dict(color='rgba(0,0,255,0.2)'), # Light blue color
                         name='Upper Envelope',
                         showlegend=False),
              row=1, col=1)

# Add lower standard deviation envelope
fig.add_trace(go.Scatter(x=dfsample.index, y=dfsample['Lower_Envelope'],
                         line=dict(color='rgba(0,0,255,0.2)'), # Light blue color
                         name='Lower Envelope',
                         fill='tonexty', # This fills the area between this trace and the next trace
                         fillcolor='rgba(0,0,255,0.3)', # More opaque blue fill
                         showlegend=False),
              row=1, col=1)


# # Add Bollinger Bands
# fig.add_trace(go.Scatter(x=dfsample.index, y=dfsample['BBU_30_2.0'],
#                          line=dict(color='rgba(0, 0, 255, 0.4)'),  # Upper Band
#                          name='Upper Bollinger Band',
#                          showlegend=True),
#               row=1, col=1)

# fig.add_trace(go.Scatter(x=dfsample.index, y=dfsample['BBM_30_2.0'],
#                          line=dict(color='rgba(0, 0, 255, 0.6)'),  # Middle Band
#                          name='Middle Bollinger Band',
#                          showlegend=True),
#               row=1, col=1)

# fig.add_trace(go.Scatter(x=dfsample.index, y=dfsample['BBL_30_2.0'],
#                          line=dict(color='rgba(0, 0, 255, 0.4)'),  # Lower Band
#                          name='Lower Bollinger Band',
#                          fill='tonexty',  # Fill between this line and the next upper band line
#                          fillcolor='rgba(0, 0, 255, 0.1)',  # Light blue fill between the bands
#                          showlegend=True),
#               row=1, col=1)


# Update layout to set background color to black and remove gridlines
fig.update_layout(
    width=1500,
    height=1200,
    sliders=[],
    paper_bgcolor='black',  # Set the background color of the entire figure
    plot_bgcolor='black',   # Set the background color of the plotting area
    xaxis_showgrid=False,   # Remove x-axis gridlines
    yaxis_showgrid=False,   # Remove y-axis gridlines
)

# Show the plot
fig.show()

7. Signals based on EMA¶

In [ ]:
def ema_signal(df, backcandles):
    # Create boolean Series for conditions
    above = df['EMA_fast'] > df['EMA_slow'] # is the fast EMA above the slow EMA (bullish)
    below = df['EMA_fast'] < df['EMA_slow'] # is the fast EMA below the slow EMA (bearish)

    # Rolling window to check if condition is met consistently over the window
    above_all = above.rolling(window=backcandles).apply(lambda x: x.all(), raw=True).fillna(0).astype(bool)
    below_all = below.rolling(window=backcandles).apply(lambda x: x.all(), raw=True).fillna(0).astype(bool)

    # Assign signals based on conditions (if the fast EMA is above or below the slow EMA)
    df['EMASignal'] = 0  # Default no signal
    df.loc[above_all, 'EMASignal'] = 2  # Signal 2 where EMA_fast consistently above EMA_slow (buy signal)
    df.loc[below_all, 'EMASignal'] = 1  # Signal 1 where EMA_fast consistently below EMA_slow (sell signal)

    return df # return the dataframe which at this point will contain the signal.

df = df[-60000:] # limit the dataframe to last 60k entries.
df.reset_index(inplace=True, drop=True) # because we selected a subset of the data, we need to reset the index
# TODO: check if dfsample is the correct dataframe to use.
df = ema_signal(dfsample,  7) # get signals from the EMA strategy using 7 backcandles (7x5minute = 35 minutes)
In [ ]:
df
Out[ ]:
Gmt time Open High Low Close Volume EMA_slow EMA_fast ATR NW_Fitted Upper_Envelope Lower_Envelope BBL_30_2.0 BBM_30_2.0 BBU_30_2.0 BBB_30_2.0 BBP_30_2.0 EMASignal
0 2023-02-01 00:00:00 1.08605 1.08619 1.08583 1.08604 2184.41 NaN NaN NaN 1.086118 1.087100 1.085137 NaN NaN NaN NaN NaN 0
1 2023-02-01 00:15:00 1.08604 1.08623 1.08583 1.08609 2373.76 NaN NaN NaN 1.086109 1.087091 1.085127 NaN NaN NaN NaN NaN 0
2 2023-02-01 00:30:00 1.08608 1.08637 1.08604 1.08630 1649.21 NaN NaN NaN 1.086089 1.087071 1.085107 NaN NaN NaN NaN NaN 0
3 2023-02-01 00:45:00 1.08629 1.08638 1.08606 1.08622 2071.47 NaN NaN NaN 1.086055 1.087037 1.085073 NaN NaN NaN NaN NaN 0
4 2023-02-01 01:00:00 1.08622 1.08634 1.08594 1.08607 2021.66 NaN NaN NaN 1.086005 1.086987 1.085023 NaN NaN NaN NaN NaN 0
... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ... ...
18190 2024-02-16 20:45:00 1.07744 1.07770 1.07729 1.07764 3288.91 1.076952 1.077122 0.000443 1.077652 1.078634 1.076670 1.074489 1.076929 1.079370 0.453217 0.645604 2
18191 2024-02-16 21:00:00 1.07764 1.07771 1.07747 1.07763 1677.19 1.076979 1.077147 0.000414 1.077638 1.078620 1.076657 1.074836 1.077047 1.079259 0.410649 0.631739 2
18192 2024-02-16 21:15:00 1.07763 1.07778 1.07758 1.07776 964.40 1.077010 1.077176 0.000384 1.077634 1.078616 1.076652 1.075116 1.077151 1.079187 0.377894 0.649531 2
18193 2024-02-16 21:30:00 1.07777 1.07778 1.07761 1.07772 1957.77 1.077038 1.077203 0.000353 1.077632 1.078614 1.076651 1.075506 1.077264 1.079021 0.326276 0.629830 2
18194 2024-02-16 21:45:00 1.07773 1.07775 1.07747 1.07749 1693.33 1.077055 1.077217 0.000343 1.077631 1.078613 1.076649 1.075802 1.077347 1.078891 0.286670 0.546410 2

18176 rows × 18 columns

8. Calculate total signal for the Nadaraya Watson envelope.¶

In [ ]:
def total_signal(df):
    # Vectorized conditions for total_signal (buy when EMA signal is 2 and close price is below lower envelope, sell when EMA signal is 1 and close price is above upper envelope)
    condition_buy = (df['EMASignal'] == 2) & (df['Close'] <= df['Lower_Envelope'])
    condition_sell = (df['EMASignal'] == 1) & (df['Close'] >= df['Upper_Envelope'])

    # Assigning signals based on conditions
    df['Total_Signal'] = 0  # Default no signal
    df.loc[condition_buy, 'Total_Signal'] = 2
    df.loc[condition_sell, 'Total_Signal'] = 1

total_signal(dfsample)
In [ ]:
# Count the signals
dfsample["TotalSignal"]=dfsample.Total_Signal
dfsample.TotalSignal.value_counts()
Out[ ]:
0    17849
2      183
1      144
Name: TotalSignal, dtype: int64

Point of the total signal which takes under the consideration EMA and Nadaraya Watson envelope.¶

In [ ]:
def pointpos(x):
    if x['TotalSignal']==2:
        return x['Low']-1e-4
    elif x['TotalSignal']==1:
        return x['High']+1e-4
    else:
        return np.nan

df['pointpos'] = df.apply(lambda row: pointpos(row), axis=1)

Visualise the total signal including the EMA and Nadaraya Watson envelope.¶

In [ ]:
dfpl = df

# Create a plot with 2 rows
fig = make_subplots(rows=2, cols=1)

# Add candlestick plot on the first row
fig.add_trace(go.Candlestick(x=dfpl.index,
                             open=dfpl['Open'],
                             high=dfpl['High'],
                             low=dfpl['Low'],
                             close=dfpl['Close']),
              row=1, col=1)

# # # Add Bollinger Bands, EMA lines on the same subplot
# fig.add_trace(go.Scatter(x=dfpl.index, y=dfpl['BBL_30_2.0'],
#                          line=dict(color='green', width=1),
#                          name="BBL"),
#               row=1, col=1)
# fig.add_trace(go.Scatter(x=dfpl.index, y=dfpl['BBU_30_2.0'],
#                          line=dict(color='green', width=1),
#                          name="BBU"),
#               row=1, col=1)
# fig.add_trace(go.Scatter(x=dfpl.index, y=dfpl['EMA_fast'],
#                          line=dict(color='black', width=1),
#                          name="EMA_fast"),
#               row=1, col=1)
# fig.add_trace(go.Scatter(x=dfpl.index, y=dfpl['EMA_slow'],
#                          line=dict(color='blue', width=1),
#                          name="EMA_slow"),
#               row=1, col=1)

fig.add_trace(go.Scatter(x=dfpl.index, y=dfpl['Upper_Envelope'], line=dict(color='red', width=1), name="Upper Envelope"), row=1, col=1)
fig.add_trace(go.Scatter(x=dfpl.index, y=dfpl['Lower_Envelope'], line=dict(color='green', width=1), name="Lower Envelope"), row=1, col=1)

# Add markers for trade entry points on the same subplot
fig.add_trace(go.Scatter(x=dfpl.index, y=dfpl['pointpos'], mode="markers",
                         marker=dict(size=4, color="MediumPurple"),
                         name="entry"),
              row=1, col=1)

fig.update_layout(
    width=1500,
    height=1200,
    sliders=[],
    paper_bgcolor='black',  # Set the background color of the entire figure
    plot_bgcolor='black',   # Set the background color of the plotting area
    xaxis_showgrid=False,   # Remove x-axis gridlines
    yaxis_showgrid=False,   # Remove y-axis gridlines
)

fig.show()

9. Back test the Nadaraya Watson envelope strategy.¶

In [ ]:
dfopt = dfsample[0:] # copy of the dataframe
def SIGNAL():
    return dfopt.TotalSignal # 0 none 1 sell 2 buy

class MyStrat(Strategy):
    mysize = 3000 # size of trades
    slcoef = 1.1 # stop loss coeficient
    TPSLRatio = 1.5 # take profit and stop loss ratio

    def init(self): # sets strategy variables
        super().init()
        self.signal1 = self.I(SIGNAL)

    def next(self): # defines the trading strategy
        super().next()
        slatr = self.slcoef*self.data.ATR[-1] # stop loss adjusted to volatility
        TPSLRatio = self.TPSLRatio # take profit

        if self.signal1==1 and len(self.trades)==0:
            sl1 = self.data.Close[-1] - slatr
            tp1 = self.data.Close[-1] + slatr*TPSLRatio
            self.buy(sl=sl1, tp=tp1, size=self.mysize)

        elif self.signal1==2 and len(self.trades)==0:
            sl1 = self.data.Close[-1] + slatr
            tp1 = self.data.Close[-1] - slatr*TPSLRatio
            self.sell(sl=sl1, tp=tp1, size=self.mysize)

bt = Backtest(dfopt, MyStrat, cash=250, margin=1/30)
stats, heatmap = bt.optimize(slcoef=[i/10 for i in range(10, 26)],
                    TPSLRatio=[i/10 for i in range(10, 26)],
                    maximize='Return [%]', max_tries=300,
                        random_state=0,
                        return_heatmap=True)
stats
/tmp/ipykernel_11488/79916414.py:29: UserWarning:

Data index is not datetime. Assuming simple periods, but `pd.DateTimeIndex` is advised.

Backtest.optimize:   0%|          | 0/8 [00:00<?, ?it/s]
Out[ ]:
Start                                     0.0
End                                   18194.0
Duration                              18194.0
Exposure Time [%]                    9.804137
Equity Final [$]                   105.122141
Equity Peak [$]                    260.391569
Return [%]                         -57.951144
Buy & Hold Return [%]               -0.787264
Return (Ann.) [%]                         0.0
Volatility (Ann.) [%]                     NaN
Sharpe Ratio                              NaN
Sortino Ratio                             NaN
Calmar Ratio                              0.0
Max. Drawdown [%]                   -61.24997
Avg. Drawdown [%]                  -15.773511
Max. Drawdown Duration                16788.0
Avg. Drawdown Duration            2988.166667
# Trades                                 67.0
Win Rate [%]                        17.910448
Best Trade [%]                       1.105764
Worst Trade [%]                     -0.508768
Avg. Trade [%]                      -0.067063
Max. Trade Duration                     278.0
Avg. Trade Duration                 25.656716
Profit Factor                        0.638838
Expectancy [%]                      -0.066407
SQN                                  -1.50331
_strategy                 MyStrat(slcoef=2...
_equity_curve                        Equit...
_trades                       Size  EntryB...
dtype: object
In [ ]:
import plotly.graph_objects as go
from plotly.subplots import make_subplots

def plot_trades_plotly(df, strategy):
    """Function to plot trades, including entries, exits, SL, TP using Plotly."""

    entries = strategy.entries
    exits = strategy.exits
    sl_levels = strategy.sl_levels
    tp_levels = strategy.tp_levels
    trade_types = strategy.trade_types

    # Create a plot with 1 row (main candlestick chart)
    fig = make_subplots(rows=1, cols=1, shared_xaxes=True)

    # Add candlestick plot with customized line colors
    fig.add_trace(go.Candlestick(
        x=df.index,
        open=df['Open'],
        high=df['High'],
        low=df['Low'],
        close=df['Close'],
        increasing=dict(line=dict(color='rgba(0, 255, 0, 0.6)', width=1), fillcolor='rgba(0, 255, 0, 0.6)'),
        decreasing=dict(line=dict(color='rgba(255, 0, 0, 0.6)', width=1), fillcolor='rgba(255, 0, 0, 0.6)')
    ))

    # Plot entry points
    for i, (entry_time, entry_price, trade_type) in enumerate(entries):
        color = 'green' if trade_type == 'long' else 'red'
        symbol = 'triangle-up' if trade_type == 'long' else 'triangle-down'
        fig.add_trace(go.Scatter(
            x=[entry_time], y=[entry_price],
            mode='markers',
            marker=dict(symbol=symbol, color=color, size=10),
            name=f'Entry ({trade_type.capitalize()})',
            hovertemplate=f'Entry ({trade_type.capitalize()}): %{y:.2f}<extra></extra>'
        ))

        # Add stop loss (SL) and take profit (TP) lines
        fig.add_trace(go.Scatter(
            x=[entry_time, entry_time],
            y=[sl_levels[i], tp_levels[i]],
            mode='lines',
            line=dict(dash='dash', color='rgba(255, 0, 0, 0.5)' if trade_type == 'long' else 'rgba(0, 255, 0, 0.5)'),
            hoverinfo='skip',
            showlegend=False
        ))

    # Plot exit points
    for i, (exit_time, exit_price) in enumerate(exits):
        fig.add_trace(go.Scatter(
            x=[exit_time], y=[exit_price],
            mode='markers',
            marker=dict(symbol='circle', color='orange', size=10),
            name=f'Exit',
            hovertemplate=f'Exit: %{y:.2f}<extra></extra>'
        ))

    # Add Nadaraya-Watson fitted line
    fig.add_trace(go.Scatter(
        x=df.index, y=df['NW_Fitted'],
        line=dict(color='green', width=2),
        name="Nadaraya-Watson Fit"
    ))

    # Add upper and lower envelopes (standard deviation bands)
    fig.add_trace(go.Scatter(
        x=df.index, y=df['Upper_Envelope'],
        line=dict(color='rgba(0,0,255,0.2)'),  # Light blue color for upper envelope
        name='Upper Envelope',
        showlegend=False
    ))

    fig.add_trace(go.Scatter(
        x=df.index, y=df['Lower_Envelope'],
        line=dict(color='rgba(0,0,255,0.2)'),  # Light blue color for lower envelope
        name='Lower Envelope',
        fill='tonexty',  # Fills the area between the lower envelope and the upper envelope
        fillcolor='rgba(0,0,255,0.3)',  # More opaque blue fill
        showlegend=False
    ))

    # Customize layout: set background color, gridlines, and make the chart zoom-friendly
    fig.update_layout(
        width=1500,
        height=800,
        sliders=[],
        paper_bgcolor='black',  # Background color of the entire figure
        plot_bgcolor='black',   # Background color of the plotting area
        xaxis_showgrid=False,   # Remove x-axis gridlines
        yaxis_showgrid=False,   # Remove y-axis gridlines
        xaxis_rangeslider_visible=False,  # Hide the range slider for cleaner zoom
        xaxis=dict(
            rangeselector=dict(
                buttons=list([
                    dict(count=1, label="1m", step="month", stepmode="backward"),
                    dict(count=6, label="6m", step="month", stepmode="backward"),
                    dict(step="all")
                ])
            ),
            type="category",  # Ensure better zooming when there are missing or irregular time intervals
            rangebreaks=[dict(bounds=["sat", "mon"])],  # Breaks for weekends, where no trading occurs
            rangeslider=dict(visible=True, thickness=0.1)  # Adds a thinner range slider for zoom
        ),
    )

    # Update y-axis for better zoom interaction
    fig.update_yaxes(fixedrange=False)

    # Show the plot
    fig.show()

# Example usage after running backtest
bt = Backtest(dfopt, MyStrat, cash=250, margin=1/30)
stats = bt.run()

# Get strategy instance and plot trades using the enhanced function
strategy_instance = bt._strategy
plot_trades_plotly(dfopt, strategy_instance)
/tmp/ipykernel_11488/2219177076.py:114: UserWarning:

Data index is not datetime. Assuming simple periods, but `pd.DateTimeIndex` is advised.

---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[27], line 119
    117 # Get strategy instance and plot trades using the enhanced function
    118 strategy_instance = bt._strategy
--> 119 plot_trades_plotly(dfopt, strategy_instance)

Cell In[27], line 7, in plot_trades_plotly(df, strategy)
      4 def plot_trades_plotly(df, strategy):
      5     """Function to plot trades, including entries, exits, SL, TP using Plotly."""
----> 7     entries = strategy.entries
      8     exits = strategy.exits
      9     sl_levels = strategy.sl_levels

AttributeError: type object 'MyStrat' has no attribute 'entries'
In [ ]:
stats["_strategy"]
Out[ ]:
<Strategy MyStrat(slcoef=2.0,TPSLRatio=2.1)>
In [ ]:
# Convert multiindex series to dataframe
heatmap_df = heatmap.unstack()
plt.figure(figsize=(10, 8))
sns.heatmap(heatmap_df, annot=True, cmap='viridis', fmt='.0f')
plt.show()
No description has been provided for this image
In [ ]:
dftest = df[:]
def SIGNAL():
    return dftest.TotalSignal

class MyStrat(Strategy):
    mysize = 3000
    slcoef = 2.6
    TPSLRatio = 2.6

    def init(self):
        super().init()
        self.signal1 = self.I(SIGNAL)

    def next(self):
        super().next()
        slatr = self.slcoef*self.data.ATR[-1]
        TPSLRatio = self.TPSLRatio

        if self.signal1==1 and len(self.trades)==0:
            sl1 = self.data.Close[-1] - slatr
            tp1 = self.data.Close[-1] + slatr*TPSLRatio
            self.buy(sl=sl1, tp=tp1, size=self.mysize)

        elif self.signal1==2 and len(self.trades)==0:
            sl1 = self.data.Close[-1] + slatr
            tp1 = self.data.Close[-1] - slatr*TPSLRatio
            self.sell(sl=sl1, tp=tp1, size=self.mysize)

bt = Backtest(dftest, MyStrat, cash=250, margin=1/30, commission=0.0002)
bt.run()
/tmp/ipykernel_11488/1347927932.py:29: UserWarning:

Data index is not datetime. Assuming simple periods, but `pd.DateTimeIndex` is advised.

Out[ ]:
Start                                     0.0
End                                   18194.0
Duration                              18194.0
Exposure Time [%]                   18.777509
Equity Final [$]                    92.970842
Equity Peak [$]                    264.467407
Return [%]                         -62.811663
Buy & Hold Return [%]               -0.787264
Return (Ann.) [%]                         0.0
Volatility (Ann.) [%]                     NaN
Sharpe Ratio                              NaN
Sortino Ratio                             NaN
Calmar Ratio                              0.0
Max. Drawdown [%]                  -64.846012
Avg. Drawdown [%]                  -11.245286
Max. Drawdown Duration                16272.0
Avg. Drawdown Duration                 1792.2
# Trades                                 60.0
Win Rate [%]                             15.0
Best Trade [%]                       1.507011
Worst Trade [%]                     -0.679918
Avg. Trade [%]                      -0.081148
Max. Trade Duration                     764.0
Avg. Trade Duration                 56.016667
Profit Factor                        0.650433
Expectancy [%]                      -0.079987
SQN                                 -1.287177
_strategy                             MyStrat
_equity_curve                        Equit...
_trades                       Size  EntryB...
dtype: object
In [ ]:
bt.plot()
Loading "original-fs" failed
Error: Cannot find module 'original-fs'
Require stack:
- /root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js
    at Module._resolveFilename (node:internal/modules/cjs/loader:1145:15)
    at Module._load (node:internal/modules/cjs/loader:986:27)
    at Module.require (node:internal/modules/cjs/loader:1233:19)
    at require (node:internal/modules/helpers:179:18)
    at i (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:3:98)
    at r.load (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:2:1637)
    at h.load (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:1:13958)
    at u (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:3:9338)
    at Object.errorback (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:3:9457)
    at h.triggerErrorback (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:1:14252)
    at /root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:1:14003
    at r.load (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:2:1654)
    at h.load (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:1:13958)
    at u (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:3:9338)
    at l._loadModule (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:3:9466)
    at l._resolve (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:4:452)
    at l.defineModule (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:3:5561)
    at Function.p [as define] (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:4:1741)
    at out-build/bootstrap-amd.js (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:4:6445)
    at /root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:1:132
    at Object.<anonymous> (/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js:4:9653)
    at Module._compile (node:internal/modules/cjs/loader:1358:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1416:10)
    at Module.load (node:internal/modules/cjs/loader:1208:32)
    at Module._load (node:internal/modules/cjs/loader:1024:12)
    at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:174:12)
    at node:internal/main/run_main_module:28:49 {
  code: 'MODULE_NOT_FOUND',
  requireStack: [
    '/root/.vscode-server/bin/fee1edb8d6d72a0ddff41e5f71a671c23ed924b9/out/server-cli.js'
  ],
  phase: 'loading',
  moduleId: 'original-fs',
  neededBy: [ 'fs' ]
}
Here are the modules that depend on it:
[ 'fs' ]
Out[ ]:
GridPlot(
id = 'p1343', …)
align = 'auto',
aspect_ratio = None,
children = [(figure(id='p1046', ...), 0, 0), (figure(id='p1145', ...), 1, 0), (figure(id='p1002', ...), 2, 0), (figure(id='p1202', ...), 3, 0), (figure(id='p1277', ...), 4, 0)],
cols = None,
context_menu = None,
css_classes = [],
css_variables = {},
disabled = False,
elements = [],
flow_mode = 'block',
height = None,
height_policy = 'auto',
js_event_callbacks = {},
js_property_callbacks = {},
margin = None,
max_height = None,
max_width = None,
min_height = None,
min_width = None,
name = None,
resizable = False,
rows = None,
sizing_mode = 'stretch_width',
spacing = 0,
styles = {},
stylesheets = [],
subscribed_events = PropertyValueSet(),
syncable = True,
tags = [],
toolbar = Toolbar(id='p1342', ...),
toolbar_location = 'right',
visible = True,
width = None,
width_policy = 'auto')
In [ ]:
def gaussian_kernel(x, bandwidth):
    return (1 / (np.sqrt(2 * np.pi) * bandwidth)) * np.exp(-0.5 * ((x / bandwidth) ** 2))

def compute_weights(X, x, bandwidth):
    weights = [gaussian_kernel(x - i, bandwidth) for i in X]
    weights /= np.sum(weights) # Normalize to make the weights sum to 1
    return weights

# Generate synthetic price data
np.random.seed(0)
prices = np.array([2 for i in range(0, 100)])  # 100 price points

# Select the current price point (most recent)
current_price_index = 99  # Last price in the series
current_price = prices[current_price_index]

# Bandwidth (standard deviation of the kernel)
bandwidth = 10

# Compute weights for all preceding points
weights = compute_weights(range(0, 99), current_price_index, bandwidth)

# Plot the weights distribution
plt.figure(figsize=(10, 5))
plt.bar(range(current_price_index), weights, color='blue')
plt.title('Weight Distribution for Nadaraya-Watson Estimator')
plt.xlabel('Index of Price Points')
plt.ylabel('Weight')
plt.grid(False)
plt.gca().set_facecolor('black') # Change background color of the plot to black
plt.show()
No description has been provided for this image
In [ ]: